#ifndef CPU_H
#define CPU_H
#include <stdint.h>
#include <stdio.h>
#include "difftest.h"
#include <unistd.h>

class cpu
{
public:
    
    uint8_t * ram;
    class Context{
        public:
        uint64_t pc;
        uint64_t xregs [32];
        uint64_t csr  [4096];
    } curContext;
    instr_commit_t curCommitState;
    bool brunch_valid ;
    uint64_t brunch_addr ;
    
    cpu(const char *img){
        assert(img != NULL); 
        printf("The image is %s\n", img);
         // initialize memory using Linux mmap
        printf("Using simulated %luMB RAM\n", EMU_RAM_SIZE / (1024 * 1024));
        ram = (uint8_t *)mmap(NULL, EMU_RAM_SIZE, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
        if (ram == (uint8_t *)MAP_FAILED) {
            printf("Cound not mmap 0x%lx bytes\n", EMU_RAM_SIZE);
            assert(0);
        }

        int ret;
        FILE *fp = fopen(img, "rb");
        if (fp == NULL) {
            printf("Can not open '%s'\n", img);
            assert(0);
        }

        fseek(fp, 0, SEEK_END);
        long img_size = ftell(fp);
        if (img_size > EMU_RAM_SIZE) {
            img_size = EMU_RAM_SIZE;
        }

        fseek(fp, 0, SEEK_SET);
        ret = fread(ram, img_size, 1, fp);

        assert(ret == 1);
        fclose(fp);

        pc = 0x80000000;
        for(int i=0;i<32;i++){
            xregs[i] = 0;
        }
    }

    ~cpu(){
        munmap(ram, EMU_RAM_SIZE);
    };

    bool step();

    bool stateSync();

    void contextDump(); 
    
    uint64_t read(uint64_t addr, int len) {
        switch (len) {
            case 1: return *(uint8_t  *)(ram + addr);
            case 2: return *(uint16_t *)(ram + addr);
            case 4: return *(uint32_t *)(ram + addr);
            case 8: return *(uint64_t *)(ram + addr);
            default: return 0;
        }
    }   

    void write(uint64_t addr, int len, uint64_t data) {
        switch (len) {
            case 1: *(uint8_t  *)(ram + addr) = data; return;
            case 2: *(uint16_t *)(ram + addr) = data; return;
            case 4: *(uint32_t *)(ram + addr) = data; return;
            case 8: *(uint64_t *)(ram + addr) = data; return;
            default:return;
        }
    }

};

#endif